package com.leilei.serviceProject.domain.strategy.service.armory;

import com.leilei.serviceProject.domain.strategy.model.entity.StrategyAwardEntity;
import com.leilei.serviceProject.domain.strategy.model.entity.StrategyEntity;
import com.leilei.serviceProject.domain.strategy.model.entity.StrategyRuleEntity;
import com.leilei.serviceProject.domain.strategy.model.valobj.StrategyAwardCountSurplusVo;
import com.leilei.serviceProject.domain.strategy.repository.IStrategyRepository;
import com.leilei.serviceProject.types.common.Constants;
import com.leilei.serviceProject.types.enums.ResponseCode;
import com.leilei.serviceProject.types.exception.AppException;
import org.redisson.api.RBlockingQueue;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;

/**
 * @author yanglei
 * @description 策略装配库(兵工厂)，负责初始化策略计算
 * @create 2024/10/15 16:23
 */
@Service
public class StrategyArmoryDispatch implements IStrategyArmory, IStrategyDispatch {

    @Resource
    private IStrategyRepository strategyRepository;

    @Override
    public boolean assembleLotteryStrategy(Long strategyId) {
        // 1. 抽奖范围和抽奖概率装配
        // 根据strategyId查询策略奖品配置（为了获取策略奖品表strategy_award表配置的奖品概率）
        List<StrategyAwardEntity> strategyAwardEntityList = strategyRepository.queryStrategyAwardList(strategyId);

        // 2 缓存奖品库存【用于decr扣减库存使用】
        for (StrategyAwardEntity strategyAward : strategyAwardEntityList) {
            Integer awardId = strategyAward.getAwardId();
            Integer awardCount = strategyAward.getAwardCount();
            cacheStrategyAwardCount(strategyId, awardId, awardCount);
        }

        // 如果查询策略奖品为空（查不到该strategyId对应的策略奖品），那也就不需要抽奖策略装配，
        // return true(装配成功)和 return false(装配失败) 对于结果来说都是一样的，但是return false(装配失败)更符合业务含义
        if (null == strategyAwardEntityList || strategyAwardEntityList.isEmpty()) {
            return false;
        }
        // 策略装配
        // 抽奖范围装配：big_market_strategy_award_range_key_ + strategyId 例如：big_market_strategy_award_range_key_10001的范围为10000个
        // 抽奖概率装配：big_market_strategy_award_rate_key_  + strategyId 例如：big_market_strategy_award_rate_key_100001 的值为每种奖品概率*奖品总个数的集合的乱序
        assembleLotteryStrategy(String.valueOf(strategyId), strategyAwardEntityList);

        // 2. 权重抽奖范围和权重抽奖概率装配
        // 根据strategyId查询策略配置（strategy表）：为了获取ruleModels中关于rule_weight（权重）的配置
        // 优先从redis获取，如果没有查询数据库，更新至redis。big_market_strategy_key_ + strategyId
        StrategyEntity strategyEntity = strategyRepository.queryStrategyEntityByStrategyId(strategyId);

        // 获取 rule_models: rule_weight
        String ruleWeight = strategyEntity.getRuleWeight();
        // 如果查询策略权重配置为null，return true和return false结果是一样的，return false含义是策略配置和权重配置都失败了，return true含义是策略配置成功，权重配置失败
        if (null == ruleWeight) {
            return true;
        }

        // 查询策略规则配置 先从redis查询，如果没有在查询数据库，更新至redis
        // big_market_strategy_rule_ + strategyId：rule_model: rule_weight, rule_value: 4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109
        StrategyRuleEntity strategyRuleEntity = strategyRepository.queryStrategyRuleEntity(strategyId, ruleWeight);
        if (null == strategyRuleEntity.getRuleValue()) {
            throw new AppException(ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getCode(), ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getInfo());
        }
        // ruleWeightValue数据格式：4000:102,103,104,105 [102,103,104]   5000:102,103,104,105,106,107 [102,103,104,105,106,107]
        Map<String, List<Integer>> ruleWeightValueMap = strategyRuleEntity.getRuleValue(strategyRuleEntity.getRuleValue());

        Set<String> ruleWeightValueMapKeys = ruleWeightValueMap.keySet();
        for (String ruleWeightValueMapKey : ruleWeightValueMapKeys) {
            // [102,103,104] [102,103,104,105,106,107]
            List<Integer> ruleWeightValue = ruleWeightValueMap.get(ruleWeightValueMapKey);
            List<StrategyAwardEntity> strategyAwardEntityListClone = new ArrayList<>(strategyAwardEntityList);
            strategyAwardEntityListClone.removeIf(entity -> !ruleWeightValue.contains(entity.getAwardId()));
            // 权重策略装配
            // 权重抽奖范围装配：big_market_strategy_award_range_key_ + strategyId + ruleWeightValueMapKey 例如：big_market_strategy_award_range_key_10001_4000:102,103,104,105的范围个数为6个
            // 权重抽奖概率装配：big_market_strategy_award_rate_key_  + strategyId + ruleWeightValueMapKey 例如：big_market_strategy_award_rate_key_100001_4000:102,103,104,105 的值为102,103,104,105每种奖品概率*奖品总个数的集合的乱序
            assembleLotteryStrategy(strategyId.toString().concat("_").concat(ruleWeightValueMapKey), strategyAwardEntityListClone);
        }
        return true;
    }

    @Override
    public Integer getRandomAwardId(Long strategyId) {
        int rateRange = strategyRepository.queryRateRange(strategyId);
        return strategyRepository.getStrategyAwardRateSearchTable(String.valueOf(strategyId), new SecureRandom().nextInt(rateRange));
    }

    @Override
    public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) {
        String cacheKey = String.valueOf(strategyId).concat("_").concat(ruleWeightValue);
        int rateRange = strategyRepository.queryRateRange(cacheKey);
        return strategyRepository.getStrategyAwardRateSearchTable(cacheKey, new SecureRandom().nextInt(rateRange));
    }

    private void assembleLotteryStrategy(String key, List<StrategyAwardEntity> strategyAwaryEntityList) {
        // 1. 获取概率最小值
        BigDecimal minAwardRate = strategyAwaryEntityList.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .min(BigDecimal::compareTo)
                .orElse(BigDecimal.ZERO);

        // 2. 获取概率总和
        /*
         * 为什么要获取概率总和，每个策略的总和不是一定是100吗？难道不是？

         * 奖品总概率可以大于1,如 总概率/最小概率  1.01/0.0001=100100
         * 	10000份随机积分,100份一/二/三等奖
         * 	总概率小于1同理,实际概率是动态分配的 ,这也算一种健壮性,数据库概率总和不为1也不会出错
         */
        BigDecimal totalAwardRate = strategyAwaryEntityList.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        // 3. 用 1 % 0.0001 获得概率范围，百分位、千分位、万分位
        /*
         * 为什么表示总的概率范围值除最小概率值，不保留小数，并向上取整，可以取得概率范围？

         * 首先,奖品数不能是小数
         * 1. 一般情况 总概率/最小概率 能整除的 不保留小数，并向上取整对结果没有影响。如 1/0.0001 = 10000,1.01/0.0001 = 10100
         * 2. 总概率/最小概率不能整除情况，如 1/0.0011 = 909.0909 向上取整为910。
         * 下面第5步生成概率查找表时也是向上取整，910 * 0.0011 = 1.001，取成 2 。910 * 0.9989 = 908.99，取成 909，909 + 2 = 911，也就是说每种概率的奖品
         * 经过这种计算之后都有可能会多，rateRange.intValue()： rateRange = 910 规定了奖品的数量为910，所以策略概率查找表最后一个概率之前的奖品数量都有可能多，
         * 最后一个概率奖品有可能少，这会导致随机抽奖的概率和数据库里的奖品概率有差别。
         * 3. 总概率/最小概率不能整除情况，如 1/0.0011 = 909.0909 向下取整为909。
         * 下面第5步生成概率查找表时改为向下取整，910 * 0.0011 = 1.001，取成 1 。910 * 0.9989 = 908.99，取成 908，908 + 1 = 909，也就是说每种概率的奖品
         * 都有可能少，同样这会导致随机抽奖的概率和数据库里的奖品概率有差别。
         *
         * 这是一个bug，后面会优化。bug代码分支：营销服务_第3节_策略概率装配处理
         */
        BigDecimal rateRange = totalAwardRate.divide(minAwardRate, 0, RoundingMode.CEILING);

        // 4.生成策略奖品概率查找表
        List<Integer> strategyAwardRateSearchTable = new ArrayList<>(rateRange.intValue());
        for (StrategyAwardEntity strategyAwardEntity : strategyAwaryEntityList) {
            // 计算每个策略奖品的数量，填充到策略奖品概率查找表
            for (int i = 0; i < rateRange.multiply(strategyAwardEntity.getAwardRate()).setScale(0, RoundingMode.CEILING).intValue(); i++) {
                strategyAwardRateSearchTable.add(strategyAwardEntity.getAwardId());
            }
        }

        // 5.对第5步生成的策略奖品概率查找表进行乱序操作
        Collections.shuffle(strategyAwardRateSearchTable);

        // 6.生成map集合
        Map<Integer, Integer> shuffleStrategyAwardRateSearchTable = new HashMap<>(strategyAwardRateSearchTable.size());
        for (int i = 0; i < strategyAwardRateSearchTable.size(); i++) {
            shuffleStrategyAwardRateSearchTable.put(i, strategyAwardRateSearchTable.get(i));
        }

        // 7. 存到redis
        strategyRepository.restoreStrategyAwardRateSearchTable(key, shuffleStrategyAwardRateSearchTable.size(), shuffleStrategyAwardRateSearchTable);
    }

    @Override
    public boolean awardStockDeduction(Long strategyId, Integer awardId) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        return strategyRepository.awardStockDeduction(cacheKey);
    }

    /**
     * 缓存奖品库存到Redis
     *
     * @param strategyId 策略ID
     * @param awardId    奖品ID
     * @param awardCount 奖品库存
     */
    private void cacheStrategyAwardCount(Long strategyId, Integer awardId, Integer awardCount) {
        String cacheKey = Constants.RedisKey.STRATEGY_AWARD_COUNT_KEY + strategyId + Constants.UNDERLINE + awardId;
        strategyRepository.cacheStrategyAwardCount(cacheKey, awardCount);
    }
}
